<template>
    <router-view v-slot="{ Component, route }">
        <transition
            v-bind="$attrs"
            :name="route.meta.transition"
            :enter-active-class="route.meta.enterActiveClass"
            :leave-active-class="route.meta.leaveActiveClass"
            @before-enter="beforeEnter($event, route.meta.previousRouterScrollPosition)"
            @before-leave="beforeLeave"
            @after-enter="afterEnter($event, route.meta.previousRouterScrollPosition)"
            @after-leave="afterLeave"
            @enter-cancelled="enterCancelled"
            @leave-cancelled="leaveCancelled"
        >
            <component :key="routeKey" :is="Component" />
        </transition>
    </router-view>
</template>

<script setup lang="ts">
import { useRouter } from "vue-router";
import { onMounted } from "vue";
import { RouterMetaTransition, Transition, RouterHistory } from "./types/index";
import "./style.css";

// Props 定义
interface Props {
    defaultClassTransition?: string;
    bodyClassEnterTransitonActive?: string;
    bodyClassLeaveTransitonActive?: string;
    routeKey: string;
    ignoreFirstLoad?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
    defaultClassTransition: "transition-active",
    bodyClassEnterTransitonActive: "body-enter-transition-active",
    bodyClassLeaveTransitonActive: "body-leave-transition-active",
    ignoreFirstLoad: true
});

const router = useRouter();
const history: Array<RouterHistory> = [];
onMounted(() => {
    history.push({
        path: router.currentRoute.value.path,
        previousRouterScrollPosition: window.scrollY
    });
});
// 转场动画方法
const beforeEnter = (el: Element, previousRouterScrollPosition: number) => {
    const htmlEl = el as HTMLElement;
    const currentScrollPosition = window.scrollY;
    htmlEl.style.top = currentScrollPosition - previousRouterScrollPosition + "px";
    document.body.classList.add(props.bodyClassEnterTransitonActive);
};

const afterEnter = (el: Element, previousRouterScrollPosition: number) => {
    const htmlEl = el as HTMLElement;
    htmlEl.style.top = "";
    document.body.classList.remove(props.bodyClassEnterTransitonActive);
    window.scrollTo(0, previousRouterScrollPosition || 0);
};

const enterCancelled = (el: Element) => {
    const htmlEl = el as HTMLElement;
    htmlEl.style.top = "";
    document.body.classList.remove(props.bodyClassEnterTransitonActive);
};

const beforeLeave = () => {
    document.body.classList.add(props.bodyClassLeaveTransitonActive);
};

const afterLeave = () => {
    document.body.classList.remove(props.bodyClassLeaveTransitonActive);
};

const leaveCancelled = () => {
    document.body.classList.remove(props.bodyClassLeaveTransitonActive);
};
// 路由切换处理
router.afterEach((to, from) => {
    to.meta.previousRouterScrollPosition = 0;
    // Do not show animation on the first page load
    if (props.ignoreFirstLoad && !history.length) {
        to.meta.transition = "";
        history.push({
            path: to.path,
            previousRouterScrollPosition: window.scrollY
        });
        return;
    }

    // Add the new path to the history.
    if (to.path !== from.path) {
        const beforeLast = history[history.length - 2];
        if (!beforeLast || beforeLast.path !== to.path) {
            history.push({
                path: to.path,
                previousRouterScrollPosition: window.scrollY
            });
        } else {
            // Remove the last path from the history in case the router go back to beforeLast router
            const lastHistory = history.pop();
            if (lastHistory) {
                to.meta.previousRouterScrollPosition = lastHistory.previousRouterScrollPosition;
            }
        }
    }

    // Decide which transition to use
    const toTransistions: RouterMetaTransition | undefined = to.meta?.transitions as RouterMetaTransition;
    const fromTransistions: RouterMetaTransition | undefined = from.meta?.transitions as RouterMetaTransition;

    const transition: Transition = {
        name: undefined,
        enterClass: undefined,
        leaveClass: undefined
    };

    /**
     * If two routes have the same name or same transitionID, use self animation
     * If two routes have its own transition defined, use the transition that have higher priority (lower number)
     * If there is only one transition defined, use it
     * If there is no transition defined, ofcouse, no transition
     */
    if (
        toTransistions?.selfEnter &&
        (to.name === from.name || (toTransistions?.transitionID && toTransistions?.transitionID === fromTransistions?.transitionID))
    ) {
        setTransition(toTransistions?.selfEnter, "enter");
        return;
    }

    // Choose the highest priority transition (lowest number)
    if (toTransistions && fromTransistions) {
        fromTransistions.priority < toTransistions.priority
            ? setTransition(fromTransistions.leave, "leave")
            : setTransition(toTransistions.enter, "enter");
        return;
    }
    if (toTransistions) {
        setTransition(toTransistions.enter, "enter");
        return;
    }
    if (fromTransistions) {
        setTransition(fromTransistions.leave, "leave");
        return;
    }

    function setTransition(routeTransition: Transition | string | undefined, highPriorityElement: string) {
        if (!routeTransition) return;
        if (typeof routeTransition === "string") {
            transition.name = routeTransition;
        } else {
            transition.name = routeTransition.name;
            transition.enterClass = routeTransition.enterClass;
            transition.leaveClass = routeTransition.leaveClass;
        }

        if (highPriorityElement == "enter") {
            transition.enterClass = "high-priority-transition " + (transition.enterClass || "");
        } else {
            transition.leaveClass = "high-priority-transition " + (transition.leaveClass || "");
        }

        to.meta.transition = transition.name ? props.defaultClassTransition + " " + transition.name : undefined;
        to.meta.enterActiveClass = transition.enterClass;
        to.meta.leaveActiveClass = transition.leaveClass;
    }
});
</script>
